package com.gxd.redis.utils;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.gxd.commons.utils.FastJsonUtils;
import com.gxd.commons.utils.JsonUtils;
import com.gxd.commons.utils.StringUtils;
import com.gxd.redis.config.RedisProperties;
import org.apache.commons.collections.map.HashedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.*;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author:gxd
 * @Description:
 * @Date: 11:53 2018/1/5
 * @Modified By:
 */
@Component
public class RedisUtils {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @SuppressWarnings("SpringJavaAutowiringInspection") //加这个注解让IDE 不报: Could not autowire
    @Resource
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired(required = false)
    @Qualifier("gxdRedisProperties")
    private RedisProperties redisProperties;
    /** 若实现最大记录是 10，则应该为 9 */
    private static int maxCount = 99;
    /**
     * 普通缓存放入
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, final Object value) {
        redisTemplate.opsForValue().set(key, value);
        return true;
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time>0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 添加缓存数据（给定key已存在，不进行覆盖，直接返回false）
     * @param key
     * @param obj
     * @return 操作成功返回true，否则返回false
     * @throws DataAccessException
     */
    public <T> boolean setNX(String key, T obj) throws DataAccessException {
        final byte[] bkey = key.getBytes();
        final byte[] bvalue = new GenericJackson2JsonRedisSerializer().serialize(obj);
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(bkey, bvalue);
            }
        });

        return result;
    }
    public Set<byte[]> keys(final String key){
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        Set<byte[]> bytesSet = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
            @Override
            public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keys = key.getBytes();
                return connection.keys(keys);
            }
        });

        return bytesSet;
    }
    /**
     * 普通缓存get
     * @param key
     * @return
     */
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null?null:redisTemplate.opsForValue().get(key);
    }

    private final static String GET_AND_EXTEND_TIME_SCRIPT =
                  "if redis.call('exists',KEYS[1]) == 1 " +
                    "then " +
                       "redis.call('EXPIRE',KEYS[1],ARGV[1]+0) " +
                    "return redis.call('GET',KEYS[1])";
    public Object _getAndExtendTime(String key, long delta,long time,TimeUnit unit){
        List<String> keys = Lists.newArrayList();
        keys.add(key);
        List<String> args = Lists.newArrayList();
        args.add(String.valueOf(delta));
        args.add(String.valueOf(time));
        return evalObject(keys, GET_AND_EXTEND_TIME_SCRIPT,args);
    }
    /**
     * 获取值并延长时间
     * @param key
     * @return
     */
    @Deprecated
    @Transactional
    public Object getAndExtendTime(String key,long time){
        Object object = redisTemplate.opsForValue().get(key);
        if(object != null){
            redisTemplate.opsForValue().set(key, object, time, TimeUnit.SECONDS);
        }
        return object;
    }


    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public String set(String key, Object value, long time){
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return "ok";
        } catch (Exception e) {
            logger.error("异常:"+e.getMessage());
            return "false";
        }
    }

    /**
     * 自定义时间
     * @param key
     * @param value
     * @param time
     * @param timeUnit
     * @return
     */
    public String set(String key, Object value, long time,TimeUnit timeUnit){
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            } else {
                set(key, value);
            }
            return "ok";
        } catch (Exception e) {
            logger.error("异常:"+e.getMessage());
            return "false";
        }
    }

    /**
     * @param key
     * @param value
     */
    public void set(byte[] key, byte[] value) {
        this.set(key, value, 0L);
    }
    /**
     * @param key
     * @param value
     * @param liveTime
     */
    public void set(final byte[] key, final byte[] value, final long liveTime) {
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(key, value);
                if (liveTime > 0) {
                    connection.expire(key, liveTime);
                }
                return 1L;
            }
        });
    }

    public  <T> T getObject(final String key, Class<T> elementType) {
        return redisTemplate.execute(new RedisCallback<T>() {
            @Override
            public T doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(keybytes)) {
                    byte[] valuebytes = connection.get(keybytes);
                    GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
                    T value = (T) genericJackson2JsonRedisSerializer.deserialize(valuebytes);
                    return value;
                }
                return null;
            }
        });
    }

    public Object getObject(final String key){
        return redisTemplate.opsForValue().get(key);
    }
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param value 值
     * @param expire 时间(秒)
     * @return
     */

    public void expire(final String key,final Object value, long expire) {
         redisTemplate.opsForValue().set(key, value,expire,TimeUnit.SECONDS);
    }

    /**
     * 指定主键在某个时间点过期
     * @param key
     * @param delta
     * @param time
     * @param unit
     */
    private static final String INCR_AND_EXPIRE_SCRIPT =
            "local current = redis.call('incrBy',KEYS[1],ARGV[1]);" +
                    "           if current == tonumber(ARGV[1]) then" +
                    "             local t = redis.call('ttl',KEYS[1]);" +
                    "          if t == -1 then " +
                    "            redis.call('expire',KEYS[1],ARGV[2])" +
                    "             end;" +
                    "            end;" +
                    "            return current";

    public void _incrAndExpire(String key, long delta,long time,TimeUnit unit){
        List<String> keys = Lists.newArrayList();
        keys.add(key);
        List<String> args = Lists.newArrayList();
        args.add(String.valueOf(delta));
        args.add(String.valueOf(time));
        eval(keys, INCR_AND_EXPIRE_SCRIPT,args);
    }

    @Deprecated
    @Transactional
    public void incrAndExpire(String key, long delta,long time,TimeUnit unit){
        if(hasKey(key)){
            redisTemplate.opsForValue().increment(key, delta);
        }else {
            redisTemplate.opsForValue().set(key,delta,time,unit);
        }
    }
    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */

    public int getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS).intValue();
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */

    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            logger.error("异常:"+e.getMessage());
            return false;
        }
    }

    public boolean delObject(Object object){
        try {
            redisTemplate.delete(object);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */

    public void del(String ... key){
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    /**
     * 批量删除以某字符串前缀的key
     * @param preStr
     */
    public void batchDel(String preStr){
        Set<Object> set = redisTemplate.keys(preStr +"*");
        Iterator<Object> it = set.iterator();
        while(it.hasNext()){
            Object keyStr = it.next();
            redisTemplate.delete(keyStr);
        }
    }
    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */

    public long incr(String key, long delta){
        if(delta < 0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */

    public long decr(String key, long delta){
        if(delta < 0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    /**
     * HashGet 获取单个值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */

    public Object hget(String key, String item){
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */

    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */

    public boolean hmset(String key, Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    @Transactional
    public boolean hmset(String key, Map<String,Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Transactional
    public Object hgetAndTime(String key,String item,long time){
        try {
            Map<Object, Object> hmget = hmget(key);
            if(hmget != null && hmget.size() > 0){
                expire(key, time);
                return hmget.get(item);
            }else{
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true 成功 false失败
         */

    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            logger.error("异常:"+e.getMessage());
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */

    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */

    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */

    public double hincr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    @Transactional
    public double hincrAndTime(String key, String item, double by,long time){
        double d = redisTemplate.opsForHash().increment(key, item, by);
        if(time > 0) {
            expire(key,item, time);
        }
        return d;
    }

    public long hincr(String key, String item, long by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    @Transactional
    public long hincrAndTime(String key, String item, long by,long time){
        long l =  redisTemplate.opsForHash().increment(key, item, by);
        if(time > 0) {
            expire(key,item, time);
        }
        return l;
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */

    public double hdecr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    public long hdecr(String key, String item, long by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */

    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */

    public boolean sHasKey(String key, Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */

    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */

    @Transactional
    public long sSetAndTime(String key, long time, Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time > 0) {
                expire(key,values, time);
            }
            return count;
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */

    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * set重命名
     * @param oldkey
     * @param newkey
     */

    public void srename(String oldkey, String newkey){
        redisTemplate.boundSetOps(oldkey).rename(newkey);
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object ...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 添加set
     * @param key
     * @param value
     */

    public void sadd(String key, String... value) {
        redisTemplate.boundSetOps(key).add(value);
    }

    /**
     * 删除set集合中的对象
     * @param key
     * @param value
     */

    public  void srem(String key, String... value) {
        redisTemplate.boundSetOps(key).remove(value);
    }

//===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */

    public List<Object> lGet(String key, long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */

    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            return 0;
        }
    }


    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */

    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */

    @Transactional
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key,value, time);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */

    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */

    @Transactional
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, value,time);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
     * @return
     */

    public Object lGetIndex(String key, long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 有序集合添加
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key,Object value,double scoure){
        redisTemplate.opsForZSet().add(key,value,scoure);
    }

    /**
     * 有序集合获取
     * @param key
     * @param from
     * @param to
     * @return
     */
    public Set<Object> rangeByScore(String key,double from,double to){
        return redisTemplate.opsForZSet().rangeByScore(key, from, to);
    }

    public <T> boolean setList(String key, List<T> list) {
        String value = FastJsonUtils.toJSONString(list);
        return set(key,value);
    }


    public <T> List<T> getList(String key,Class<T> clz) {
        String json = (String) get(key);
        if(json!=null){
            List<T> list = FastJsonUtils.toList(json, clz);
            return list;
        }
        return null;
    }

    public Long generatorId(String biz) {
        // 转成数字类型，可排序
        return incrOrderId(biz, getOrderPrefix(new Date()));
    }
    /**
     * @Description 支持一个小时100w个订单号的生成
     *
     * @author biz
     * @param prefix
     * @return
     */
    private Long incrOrderId(String biz, String prefix) {
        String orderId = null;
        String key = "geese:#{biz}:id:".replace("#{biz}", biz).concat(prefix); // 00001
        try {
            Long index = redisTemplate.opsForValue().increment(key,1L);
            orderId = prefix.concat(String.format("%1$05d", index)); // 补位操作 保证满足5位
        } catch(Exception ex) {
            logger.error("分布式订单号生成失败异常:"+ex.getMessage());
        }
        if (Strings.isNullOrEmpty(orderId)) {
            return null;
        }
        return Long.parseLong(orderId);
    }
    /**
     * @Description
     *
     * @author gj
     * @param date
     * @return
     */
    private String getOrderPrefix(Date date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        int year = c.get(Calendar.YEAR);
        int day = c.get(Calendar.DAY_OF_YEAR); // 今天是第多少天
        int hour =  c.get(Calendar.HOUR_OF_DAY);
        String dayFmt = String.format("%1$03d", day); // 0补位操作 必须满足三位
        String hourFmt = String.format("%1$02d", hour);  // 0补位操作 必须满足2位
        StringBuffer prefix = new StringBuffer();
        prefix.append((year - 2000)).append(dayFmt).append(hourFmt);
        return prefix.toString();
    }


    @Autowired(required=false)
    @Qualifier("jedisCluster")
    JedisCluster jedisCluster;
    /**
     * 分页
     * @param patternKey
     * @return
     */
    public List<String> findKeysForPage(String patternKey) {
        if(redisProperties.getNodes() != null && redisProperties.getNodes().contains(",")) {
            Set<String> set = new HashSet<>();
            String scanRet = "0";
            String scanCursor = "0";
            List<String> retList = new ArrayList<>();
            Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
            ScanParams scanParams = new ScanParams();
            scanParams.match(patternKey);
            try {
                for (String k : clusterNodes.keySet()) {
                    JedisPool jp = clusterNodes.get(k);
                    Jedis connection = jp.getResource();
                    do {
                        ScanResult<String> ret = connection.scan(scanCursor, scanParams);
                        scanRet = ret.getStringCursor();
                        if (ret.getResult().size() > 0) {
                            set.addAll(ret.getResult());
                        }
                        scanCursor = scanRet;
                    } while (!scanRet.equals("0"));
                    if (connection != null) {
                        connection.close();
                    }
                }
            } catch (Exception e) {
                logger.error("异常信息：" + e.getMessage());
                return retList;
            }
            return new ArrayList<>(set);
    }else{
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = factory.getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<String>();
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
            cursor.next();
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            logger.error("异常信息:"+e.getMessage());
        }
        return result;
     }
    }

    public long lpush(final String key, Object obj) {
        final String value = JsonUtils.toJson(obj);
        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.lPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
        });
        return result;
    }

    public long rpush(final String key, Object obj) {
        final String value = JsonUtils.toJson(obj);
        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.rPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
        });
        return result;
    }

    public String lpop(final String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] res =  connection.lPop(serializer.serialize(key));
                return serializer.deserialize(res);
            }
        });
        return result;
    }

    /**
     * 保存信息
     * @param redisKey
     * @param msg
     */
    @Transactional
    public void saveMessage(String redisKey, String msg){
        lpush(redisKey,msg);
        // 仅保留指定区间内的记录数，删除区间外的记录。下标从 0 开始，即 end 需要最大值 -1
        ltrim(redisKey, 0, maxCount);
    }

    public void ltrim(String redisKey, int i, int maxCount) {
        redisTemplate.opsForList().trim(redisKey, i, maxCount);
    }

    public List<Object> queryData(String redisKey) {
        List<Object> list = redisTemplate.opsForList().range(redisKey,0,-1);// end 为 -1 表示到末尾。因为前面插入操作时，限定了存在的记录数
        if (list == null || list.size() == 0) {
            list = new ArrayList<Object>();
        }
        return list;
    }


    /**
     * zset有序集合类型:为有序集key的成员member的offset值加上增量increment
     * @param key    key
     * @param offset    增量increment
     * @param field    集合成员
     * @return  member成员的新offset值
     */
    public Double zincrby(final String key, final Double offset, final String field) {
        return redisTemplate.execute(new RedisCallback<Double>() {
            @Override
            public Double doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.zIncrBy(
                        redisTemplate.getStringSerializer().serialize(key),
                        offset,
                        redisTemplate.getStringSerializer().serialize(field));
            }
        });
    }

    /**
     * zset有序集合类型:找到指定区间范围的数据进行返回
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<byte[]> zrangeByScore(final String key, final Double start, final Double end) {
        return redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
            @Override
            public Set<byte[]> doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.zRangeByScore(redisTemplate.getStringSerializer().serialize(key),start,end);
            }
        });
    }


    public Set<byte[]> zRevRangeByScore(final String key, final Double start, final Double end) {
        return redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
            @Override
            public Set<byte[]> doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.zRevRangeByScore(redisTemplate.getStringSerializer().serialize(key),start,end);
            }
        });
    }


    @Transactional
    public void zIncrementScore(final String key, final String field, final Double offset,long time,TimeUnit unit){
        this.redisTemplate.opsForZSet().incrementScore(key, field, offset);
        if(this.redisTemplate.getExpire(key).equals(-1L)){
            this.redisTemplate.expire(key,time,unit);
        }
    }

    @Transactional
    public List<Map>  getZrangeByScore(final String key, final Double start, final Double end){
        Set<byte[]> sets = this.zrangeByScore(key, start, end);
        List<Map> result = new ArrayList<>();
        if (null == sets || sets.size()  == 0) {
            return result;
        }
        Map map = null;
        Object object = "",value = "";
        for (byte[] set : sets) {
            map = new HashedMap();
            object = this.redisTemplate.getStringSerializer().deserialize(set);
            value = this.redisTemplate.opsForZSet().score(key,object.toString().replace("\"",""));
            map.put(object,value);
            result.add(map);
        }
        return result;
    }

    @Transactional
    public List<Map>  getZRevRangeByScore(final String key, final int count){
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = this.redisTemplate.boundZSetOps(key).reverseRangeWithScores(0, -1);
        List<Map> result = new ArrayList<>();
        if (null == typedTuples || typedTuples.size()  == 0) {
            return result;
        }
        Map map = null;
        for (ZSetOperations.TypedTuple<Object> typedTuple : typedTuples) {
            map = new HashedMap();
            map.put(typedTuple.getValue(),typedTuple.getScore());
            if(result.size() > count){
                break;
            }
            result.add(map);
        }
        return result;
    }
    /**
     * zset有序集合类型:返回有序集key中，score值在min和max之间(默认包括score值等于min或max)的成员。
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zcount(final String key, final Double start, final Double end) {
        return redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                byte[] keys = redisTemplate.getStringSerializer().serialize(key);
                return redisConnection.zCount(keys,start,end);
            }
        });
    }


    public static final int[] PRECISION = new int[]{1, 5, 60, 300, 3600, 18000, 86400};

    /**
     * 支持以秒为单位的计数器精度，分别是1秒、5秒、1分钟、5分钟、1小时、5小时
     * @param name
     * @param count
     * @param now
     */
    @Transactional
    public void updateCounter(String key,String name, double count, long now){
        //每一次更新，都要更新所有精度的计数器
        for (int prec : PRECISION) {
            long pnow = (now / prec) * prec;//当前时间片的开始时间
            String hash = String.valueOf(prec) + ':' + name;
            zAdd(key,hash, 0);
            hincr(key+":count:" + hash, String.valueOf(pnow), count);
        }
    }


    /**
     * tps控制
     * @param timeKey 计时器
     * @param counterKey 计数器
     * @param reqCount 每秒请求数
     * @return
     */
    @Transactional
    public boolean controlTPS(final String timeKey, final String counterKey, long reqCount) {
        if (!hasKey(timeKey)) {
            set(timeKey, 0, (long) 1, TimeUnit.SECONDS);
            //时间到就重新初始化为
            set(counterKey, 0, (long) 2, TimeUnit.SECONDS);
        }
        if (hasKey(timeKey) && incr(counterKey, (long) 1) >  reqCount) {
            logger.error("调用频率过快，TPS超过"+reqCount+"次/s");
            return true;
        }
        return false;
    }

    /**
     * lua 脚本,控制tps
     */
    private static final String LUA_TPS_SCRIPT =
              " local current ;"
            + " current = redis.call('incr',KEYS[1]); "
            + " if tonumber(current) == 1 then "
            + "    redis.call('expire',KEYS[1],ARGV[1]); "
            + "    return 1; "
            + " else"
            + "    if tonumber(current) < tonumber(ARGV[2]) then "
            + "     	return 1; "
            + "	   else "
            + "			return 0; "
            + "    end "
            + " end ";

    private static final String LUA_TPS_AND_COUNT_SCRIPT =
              " local current;"
            + " current = redis.call('incr',KEYS[1]); "
            + " if tonumber(current) == 1 then "
            + " 	redis.call('expire',KEYS[1],ARGV[1]); "
            + "     return {'1',redis.call('get',KEYS[2])}; "
            + " else"
            + " 	if tonumber(current) < tonumber(ARGV[2]) then "
            + "     	return {'1',redis.call('get',KEYS[2])}; "
            + "		else "
            + "			return {'-1',redis.call('get',KEYS[2])}; "
            + "     end "
            + " end ";

    /**
     * 执行 lua 脚本
     * @param keys
     * @param scriptName
     * @param args
     * @return
     */
    public Long eval(List<String> keys, String scriptName, List<String> args) {
        try {
            Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样，但是没有共同的接口，所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(scriptName, keys, args);
                }
                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(scriptName, keys, args);
                }
                return 0L;
            });
            return result;
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return 0L;
    }

    public Object evalObject(List<String> keys, String scriptName, List<String> args) {
        try {
            Object result = redisTemplate.execute((RedisCallback<Object>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样，但是没有共同的接口，所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Object) ((JedisCluster) nativeConnection).eval(scriptName, keys, args);
                }
                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Object) ((Jedis) nativeConnection).eval(scriptName, keys, args);
                }
                return null;
            });
            return result;
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    /**
     * 限流以及调用量
     * @param keys 键
     * @param args 参数
     * @return
     */
    public List<String> redisRateLimiterAndCount(List<String> keys,List<String> args) {
        try {
            List<String> result = redisTemplate.execute((RedisCallback<List<String>>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样，但是没有共同的接口，所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    JedisCluster jedisCluster = (JedisCluster) nativeConnection;
                    return (List<String>)jedisCluster.eval(LUA_TPS_AND_COUNT_SCRIPT, keys, args);
                }
                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    Jedis jedis = (Jedis) nativeConnection;
                    return (List<String>)jedis.eval(LUA_TPS_AND_COUNT_SCRIPT, keys, args);
                }
                return null;
            });
            return result;
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    /**
     * 限流
     * @param key 主键
     * @param expire 过期时间
     * @param reqCount 每秒请求数 reqCount(-1 参数无效; 0不受限制; > 0 有效)
     * @return
     */
    public Long redisRateLimiter(String key,int expire,int reqCount) {
        List<String> keys = Lists.newArrayList();
        keys.add(key);
        List<String> args = Lists.newArrayList();
        args.add(String.valueOf(expire));
        args.add(String.valueOf(reqCount));
        return eval(keys, LUA_TPS_SCRIPT,args);
    }



    /**
     * lua 日调用量脚本
     */
    private static final String LUA_COUNT_SCRIPT =
             " local current = tonumber(redis.call('get', KEYS[1]) or \"0\") ;"
            + " if current < tonumber(ARGV[2]) then "
            + "    current = redis.call('incr',KEYS[1]); "
            + " end "
            + " if tonumber(current) == 1 then "
            + " 	redis.call('expire',KEYS[1],ARGV[1]); "
            + "     return 1; "
            + " else"
            + " 	if tonumber(current) < tonumber(ARGV[2]) then "
            + "     	return 1; "
            + "		else "
            + "			return 0; "
            + "     end "
            + " end ";

    /**
     * 限量
     * @param key
     * @param expire
     * @param limit
     * @return
     */
    public Long redisQuantityLimiter(String key,int expire,int limit) {
        List<String> keys = Lists.newArrayList();
        keys.add(key);
        List<String> args = Lists.newArrayList();
        args.add(String.valueOf(expire));
        args.add(String.valueOf(limit));
        return eval(keys, LUA_COUNT_SCRIPT, args);
    }

}

